/**
 *
 */
package wsepForum.persistance;

import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Vector;

import wsepForum.common.elements.*;
import wsepForum.common.elements.AForumElement.ForumElementType;
import wsepForum.logging.ForumLogger;
import wsepForum.logging.ForumLogger.LoggerCategory;
import wsepForum.persistance.exceptions.*;

/**
 * this class implements the IPersistanceHandler interface and responsible to the connection
 * between the domain layer and the data base (this is actually the main core of the persistance
 * layer) - adding\ updating\ deleting elements that can be section\topic\message and the same with users.
 * @author Ofir Tam and Ori Ossmy
 */
public class DataHandler implements IPersistentHandler {

	/*****************************
	 * MEMBERS                   *
	 *****************************/
	/**
	 * member for the data base connector for the communication with DB
	 */
	private IDBConnector dbConnector;

	private ISearchAdaptor searchAdaptor;

	/**
	 * the general id for generating ID to elements
	 */
	public static int elementGeneralID = 1;


	/*****************************
	 * CONSTRUCTOR               *
	 *****************************/
	/**
	 * constructor to the data handler - gets only the data base connector for DB communication
	 */
	public DataHandler(IDBConnector dbConnector, ISearchAdaptor searchAdaptor) {
		this.dbConnector = dbConnector;
		this.searchAdaptor = searchAdaptor;
	}





	/*****************************
	 * METHODS                   *
	 *****************************/

	/* (non-Javadoc)
	 * @see wsepForum.persistent.IPersistentHandler#getForumElement(double)
	 */
	@Override
	public AForumElement getForumElement(int forumElementID, ForumElementType type) throws WrongElementIDException, DataBaseIOException {
		AForumElement returnedElement = this.dbConnector.read(forumElementID, type);
		return returnedElement;
	}




	/* (non-Javadoc)
	 * @see wsepForum.persistent.IPersistentHandler#getUser(java.lang.String)
	 */
	@Override
	public User getUser(String strUserName) throws WrongUserNameException, DataBaseIOException {
		User returnedUser = this.dbConnector.read(strUserName);
		returnedUser.setPassword(null);
		return returnedUser;
	}




	/* (non-Javadoc)
	 * @see wsepForum.persistent.IPersistentHandler#login(java.lang.String, java.lang.String)
	 */
	@Override
	public boolean login(String strUserName, String strPassword) throws WrongUserNameException, DataBaseIOException {
		try {
			// gets the user from the DB according to the given user Name
			User checkUser = this.dbConnector.read(strUserName);
			// return if the given password after encryption is equal to the one in the DB
			if(checkUser.getPassword().equals(MD5Encoder.EncryptString(strPassword + checkUser.getUserSalt()))) {
				this.dbConnector.setLoginTime(strUserName);
				ForumLogger.getInstance().info("User " + strUserName + " has logged in", LoggerCategory.PERSISTANCE);
				return true;
			} else {
				ForumLogger.getInstance().info("User " + strUserName + " failed login", LoggerCategory.PERSISTANCE);
				return false;
			}
		} catch (UnsupportedEncodingException e) {
			return false;
		} catch (NoSuchAlgorithmException e) {
			return false;
		}
	}




	/* (non-Javadoc)
	 * @see wsepForum.persistent.IPersistentHandler#setForumElement(double, wsepForum.common.elements.AForumElement)
	 */
	@Override
	public synchronized int setForumElement(int parentID, ForumElementType parentType, AForumElement element) throws WrongElementIDException, InvalidElementFormatException, DataBaseIOException {

		int returnedID = -1;

		// Generating ID for the new element
		elementGeneralID += element.setElementID(elementGeneralID);
		// set the new element PID
		element.setParentID(parentID);

		switch(element.elementIdetitity()) {
			case MESSAGE:

				switch(parentType) {
					case TOPIC:
						// change topic headline if first message in topic is changed
						Topic parentTopic = (Topic)this.dbConnector.read(parentID, ForumElementType.TOPIC);
						if(parentTopic.getAllMessages().size() > 0 && parentTopic.getMessage(0).getElementID() == element.getElementID()) {
							parentTopic.setHeadLine(element.getHeadLine());
						}
						// update topic
						this.dbConnector.write(parentTopic);
						//writing the message to DB.
						this.dbConnector.write(element);
						// add message to search engine
						this.searchAdaptor.add((Message)element);
						// setting the returned value to the domain layer
						returnedID = element.getElementID();

						break;

					case SECTION:

						// creating a new topic to the message and adding ID to the topic
						Topic newTopic = new Topic(elementGeneralID++, (Message)element, parentID);
						elementGeneralID += newTopic.setElementID(elementGeneralID);
						// writing the topic to DB
						this.dbConnector.write(newTopic);
						//writing the message to DB.
						this.dbConnector.write(element);
						// add message to search engine
						this.searchAdaptor.add((Message)element);
						// setting the returned ID for the domain layer
						returnedID = newTopic.getElementID();

						break;

					default:
						throw new InvalidElementFormatException("cannot write message to parent message");
					}

				break;

			case SECTION:

				if(parentType == ForumElementType.SECTION) {
					// writes the sub section the DB
					this.dbConnector.write(element);
					// setting the returned value of the domain layer
					returnedID = element.getElementID();
				} else {
					throw new InvalidElementFormatException("cannot write section to parent message or topic");
				}

				break;

			default:
				throw new InvalidElementFormatException("writing new topics is not allowed");
		}

		// return the ID given to the element
		return returnedID;
	}



	/* (non-Javadoc)
	 * @see wsepForum.persistent.IPersistentHandler#setUser(wsepForum.common.elements.User)
	 */
	@Override
	public synchronized boolean setUser(User user) throws DataBaseIOException {
		try {
			// if password needs to be changed on DB
			if(user.getPassword() != null) {
				// gets the regular password of the user
				String passwordToEncrypt = user.getPassword() + user.getUserSalt().toString();

				// encrypt the password using MD5 algorithm for setting the password in the DB
				String encryptedPassword = MD5Encoder.EncryptString(passwordToEncrypt);
				user.setPassword(encryptedPassword);
			}

			// return if the action succeeded or not
			boolean actionSucceed = this.dbConnector.write(user);
			return actionSucceed;
		} catch (UnsupportedEncodingException e) {
			return false;
		} catch (NoSuchAlgorithmException e) {
			return false;
		} catch (Exception e) {
			return false;
		}
	}




	/* (non-Javadoc)
	 * @see wsepForum.persistent.IPersistentHandler#deleteForumElement(int)
	 */
	@Override
	public synchronized AForumElement deleteForumElement(int elementID, ForumElementType type) throws WrongElementIDException, DataBaseIOException {

		try {
			// gets the element to delte from the DB
			AForumElement elementToDelete = this.dbConnector.read(elementID, type);

			// sets if parent remove is required (in case this is the first message and topic deletion
			// is required
			boolean removeParent = false;

			// gets the parent element of the element to delete.
			AForumElement parentOfElementToDelete = null;

			// checks if the element to delete is message
			if (elementToDelete.elementIdetitity() == ForumElementType.MESSAGE){

				// gets the topic that contains the message
				Topic parentTopic = (Topic)this.dbConnector.read(elementToDelete.getParentID(), ForumElementType.TOPIC);

				// remove the message from the topic and gets answer if required to delete the topic
				// (in case of first message or no more messages
				removeParent = parentTopic.removeMessage((Message)elementToDelete);

				// remove message from DB
				this.dbConnector.delete(elementID, ForumElementType.MESSAGE);

				//remove message from search engine
				this.searchAdaptor.remove((Message)elementToDelete);

				parentOfElementToDelete = parentTopic;

				// if topic is required to delete - activate the method again with the topicID
				if (removeParent) {
					return deleteForumElement(parentTopic.getElementID(), ForumElementType.TOPIC);
				}
			}

			// in case the element to delete is topic
			else if (elementToDelete.elementIdetitity() == ForumElementType.TOPIC){

				// gets the section that contains this topic
				Section parentSection = (Section)this.dbConnector.read(elementToDelete.getParentID(), ForumElementType.SECTION);

				// delete the topic from the DB
				this.dbConnector.delete(elementID, ForumElementType.TOPIC);

				//remove topic from search engine
				for(Message message : ((Topic)elementToDelete).getAllMessages()) {
					this.searchAdaptor.remove(message);
				}

				// remove the topic header from the parent section
				parentSection.removeTopicHeader((Topic)elementToDelete);

				parentOfElementToDelete = parentSection;
			}

			// in case the element to delete is section
			else if (elementToDelete.elementIdetitity() == ForumElementType.SECTION){

				// gets the section that contains this section
				Section parentSection = (Section)this.dbConnector.read(elementToDelete.getParentID(), ForumElementType.SECTION);

				//remove section from search engine
				for(SectionHeader sectionHeader : ((Section)elementToDelete).getAllSectionHeaders()) {
					this.deleteForumElement(sectionHeader.getElementID(), ForumElementType.SECTION);
				}
				for(TopicHeader topicHeader : ((Section)elementToDelete).getAllTopicHeaders()) {
					this.deleteForumElement(topicHeader.getElementID(), ForumElementType.TOPIC);
				}


				// deletes the section from the DB
				this.dbConnector.delete(elementID, ForumElementType.SECTION);

				// remove the section header from the parent section
				parentSection.removeSectionHeader((Section)elementToDelete);

				parentOfElementToDelete = parentSection;
			}

			// return the parent of the element we just deleted
			return parentOfElementToDelete;

		// in case problem with IO happened
		} catch (DataBaseIOException e) {
			throw e;
		// in case the elementID\parentID is not found
		} catch (Exception e) {
		//	return null;
			e.printStackTrace();
			throw new WrongElementIDException("Wrong elementID - can't find element: " + elementID);
		}
	}





	/* (non-Javadoc)
	 * @see wsepForum.persistent.IPersistentHandler#setUser(java.lang.String)
	 */
	@Override
	public synchronized boolean deleteUser(String strUserName) throws DataBaseIOException {

		// deletes the user from the data base using the DBConnector
		return this.dbConnector.delete(strUserName);
	}




	/*
	 * (non-Javadoc)
	 * @see wsepForum.persistance.IPersistentHandler#searchByAuthor(java.lang.String)
	 */
	@Override
	public Vector<SearchResult> searchByAuthor(String authorName) throws Exception {
		Vector<SearchResult> result = this.searchAdaptor.searchByAuthor(authorName);

		return readResults(result);
	}




	/*
	 * (non-Javadoc)
	 * @see wsepForum.persistance.IPersistentHandler#searchByContent(java.lang.String)
	 */
	@Override
	public Vector<SearchResult> searchByContent(String sentence) throws Exception {
		Vector<SearchResult> result = this.searchAdaptor.searchByContent(sentence);

		return readResults(result);
	}





	@Override
	public Vector<SearchResult> searchByDate(Date from, Date to) throws Exception {
		Vector<SearchResult> result = this.searchAdaptor.searchByDate(from, to);

		return readResults(result);
	}


	private Vector<SearchResult> readResults(Vector<SearchResult> searchResults) throws Exception{
		for(int i = 0; i < searchResults.size(); i++) {
			int id = searchResults.elementAt(i).getMessageID();
			searchResults.elementAt(i).setMessage((Message)this.getForumElement(id, ForumElementType.MESSAGE));
		}
		return searchResults;
	}


	@Override
	public void clearDB() throws Exception {
		this.dbConnector.clearDB();
		this.searchAdaptor.clearDB();
	}


	@Override
	public boolean initDB() throws DataBaseIOException {
		return this.dbConnector.initDB();
	}


	@Override
	public void close() {
		this.dbConnector.close();
		this.searchAdaptor.close();
	}


	@Override
	public double[] getMessageCountStatistics(Date date) {

		try {
			return dbConnector.getMessageCountStatistics(date);
		} catch (Exception e) {
			return null;
		}

	}


	@Override
	public double[] getUserCountStatistics(Date date) {

		try {
			return dbConnector.getUserCountStatistics(date);
		} catch (Exception e) {
			return null;
		}

	}


	@Override
	public double[] getUserMessagePerDay(Date fromDate, String userName) {

		try {
			return dbConnector.getUserMessagePerDay(fromDate, userName);
		} catch (Exception e) {
			return null;
		}

	}


	@Override
	public Vector<String> getUsernameList() {

		try {
			return this.dbConnector.getUsernameList();
		} catch (Exception e) {
			return null;
		}

	}


	@Override
	public void logout(String username) throws DataBaseIOException {
		this.dbConnector.setLogoutTime(username);
	}


}
